/* 
 * File:   XUDPServ.cpp
 * Author: minaev
 * 
 * Created on 14 Октябрь 2013 г., 11:53
 */

#include "XUDPServ.h"
#include "Xmsg.h"
#include "Xservice.h"

//POSIX threads
//#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>


#define COUNT_INPUT_BUFFERS 256
#define MAX_MSG_LEN 256

XUDPServ::XUDPServ(int listenPort) {

    //default broadcast ip 127.0.0.255
    broadcastIP = 0xff00007f;
    
    this->listenPort = listenPort;
    //last read
    this->cursorReadInputBuffer = 0;
    //last write
    this->cursorWriteInputBuffer = 0;
    this->lostMessages = 0;
    this->inputBuffer = new char*[COUNT_INPUT_BUFFERS];
    //memset(this->inputBuffer, 0, COUNT_INPUT_BUFFERS * sizeof (char*));
    this->inputBufferLens = new int[COUNT_INPUT_BUFFERS];
    this->inputBufferIp = new uint32_t[COUNT_INPUT_BUFFERS];
    //memset(this->inputBufferLens, 0, COUNT_INPUT_BUFFERS);
    this->listener = NULL;
}

XUDPServ::~XUDPServ() {
    close(this->updSocket);
    for (int i = 0; i < COUNT_INPUT_BUFFERS; i++) {
        if (inputBuffer[i] != NULL) {
            delete [] inputBuffer[i];
        }
    }
    delete [] inputBuffer;
    delete [] inputBufferLens;
    delete [] inputBufferIp;
}

/**
 * Start thread for listen port UDP
 * @return 0 - success, 1 - Bind UDP server error, 2 - Create thread error
 */
int XUDPServ::startListenThread() {
    this->updSocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

    this->sa.sin_addr.s_addr = htonl(INADDR_ANY);
    this->sa.sin_port = htons(this->listenPort);

    int res = bind(this->updSocket, (struct sockaddr*) &(this->sa), sizeof ( struct sockaddr));
    if (res < 0) {
        Xmsg::log(2, "FATAL ERROR: Bind UDP server error. Port: ",
                Xservice::itos(listenPort));
        return 1;
    }

    int broadcast = 1;
    if (setsockopt(this->updSocket, SOL_SOCKET, SO_BROADCAST,
            &broadcast, sizeof (broadcast)) == -1) {
        Xmsg::log("FATAL ERROR: Error set broadcast option");
        return 3;
    }

    if (pthread_create(&(this->listenThread),
            NULL,
            &XUDPServ::threadListenFunc,
            (void*) this) != 0) {
        Xmsg::log("FATAL ERROR: Thread listen UDP not started.");
        return 2;
    }

    return 0;
}

/**
 * Thread function for listen UDP
 * @param obj - XUDPServ object link / (void*)&XUDPServ /
 * @return 
 */
void* XUDPServ::threadListenFunc(void* obj) {
    int i;
    XUDPServ* lobj = (XUDPServ*) obj;
    char* inputBuffer = new char[MAX_MSG_LEN];
    static socklen_t slen;
    static struct sockaddr saddr;
    static uint32_t ip;
    int len = 0;
    //!!! replace to flag
    while (1) {
        //len = recv(lobj->updSocket, inputBuffer, MAX_MSG_LEN, 0);
        len = recvfrom(lobj->updSocket, inputBuffer, MAX_MSG_LEN, 0, &saddr, &slen);
        if (len > 0) {
            lobj->addInBuffer(len, inputBuffer, ((struct sockaddr_in *) &saddr)->sin_addr.s_addr);
            len = 0;
        } else {
            Xmsg::log("Receive pack: len <= 0");
        }
    }
    return NULL;
}

/**
 * Add to InputBuffer
 * @param len - lenth of buffer for add
 * @param buffer - buffer for add
 */
void XUDPServ::addInBuffer(int len, char* buffer, uint32_t ip) {
    pthread_mutex_lock(&mutexAccessBuf);
    int cursor = nextCursorWriteBuf();
    if (cursor != this->cursorReadInputBuffer) {
        if (this->inputBuffer[cursor] != NULL) {
            delete [] this->inputBuffer[cursor];
        }
        if (len > MAX_MSG_LEN) {
            len = MAX_MSG_LEN;
            Xmsg::log("Receive over MAX_MSG_LEN lenth package.");
        }
        this->inputBuffer[cursor] = new char [len];
        this->inputBufferLens[cursor] = len;
        this->inputBufferIp[cursor] = ip;
        for (int i = 0; i < len; i++) {
            this->inputBuffer[cursor][i] = buffer[i];
        }
        this->cursorWriteInputBuffer = cursor;
        if (this->listener != NULL) {
            this->listener->readUDP(); //signal to UDPScaner;
        }
        //printWriteBuffer();
    } else {
        Xmsg::log("XUDP Buffer overflow!");
        lostMessages++;
    }
    pthread_mutex_unlock(&mutexAccessBuf);
}

int XUDPServ::nextCursorWriteBuf() {
    return nextCursorBuf(this->cursorWriteInputBuffer);
}

int XUDPServ::nextCursorReadBuf() {
    return nextCursorBuf(this->cursorReadInputBuffer);
}

int XUDPServ::nextCursorBuf(int oldcursor) {
    if (oldcursor + 1 >= COUNT_INPUT_BUFFERS) {
        return 0;
    } else {
        return oldcursor + 1;
    }
}

/**
 * read message from input UDP Buffer
 * @return - next received message or NULL if not new message
 */
char* XUDPServ::readMsg() {
    int cursor = nextCursorReadBuf();
    pthread_mutex_lock(&mutexAccessBuf);
    if (cursor != nextCursorBuf(this->cursorWriteInputBuffer)) {
        pthread_mutex_unlock(&mutexAccessBuf);
        this->cursorReadInputBuffer = nextCursorReadBuf();
        return inputBuffer[cursorReadInputBuffer];
    } else {
        pthread_mutex_unlock(&mutexAccessBuf);
        return NULL;
    }
}

int XUDPServ::readLastLen() {
    return this->inputBufferLens[cursorReadInputBuffer];
}

uint32_t XUDPServ::readLastIP() {
    return this->inputBufferIp[cursorReadInputBuffer];
}

/**
 * Send UDP packet
 * @param ipaddr - little-endian IPv4 dist address (ex. 0x0100007f - 127.0.0.1)
 * @param prt
 * @param buf - send buffer
 * @param len - length send data
 * @return 0 - success, else error in send
 */
int XUDPServ::sendMsg(uint32_t ipaddr, uint16_t prt, const void* buf, int len) {
    if (buf == NULL) {
        return 2;
    }
    if(ipaddr==0xffffffff){
        ipaddr = broadcastIP;
    }
    this->saoutUDP.sin_family = PF_INET;
    this->saoutUDP.sin_addr.s_addr = ipaddr; //htonl(ipaddr);
    this->saoutUDP.sin_port = htons(prt);
    int cnt_bts = sendto(this->updSocket, buf, len, 0,
            (struct sockaddr*) &saoutUDP,
            sizeof (struct sockaddr_in));
    if (cnt_bts == len) {
        return 0;
    } else {
        Xmsg::printf("No send all bytes. Sending: %i, sended %i\n", len, cnt_bts);
        return 1;
    }
}

int XUDPServ::getLostMessages() {
    return this->lostMessages;
}

/**
 * Set listener for read UDP server
 * @param listener - new listener
 * @return - old listener or NULL
 */
XUDPListener* XUDPServ::setUDPListener(XUDPListener* listener) {
    static XUDPListener* oldListener;
    oldListener = this->listener;
    this->listener = listener;
    return oldListener;
}

void XUDPServ::setBroadcast(uint32_t ip) {
    this->broadcastIP = ip;
}

uint32_t XUDPServ::getBroadcast(){
    return this->broadcastIP;
}

void XUDPServ::printWriteBuffer() {
    this->inputBuffer[cursorWriteInputBuffer]
            [this->inputBufferLens[cursorWriteInputBuffer]] = 0;
    Xmsg::printf("CursorWrite: %i CursorRead: %i CurBufWrite: %s\n",
            this->cursorWriteInputBuffer,
            this->cursorReadInputBuffer,
            this->inputBuffer[cursorWriteInputBuffer]);
}

